package com.example.base_model.view.custom

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.util.TypedValue
import android.view.MotionEvent
import android.view.View
import com.example.base_model.R
import kotlin.math.abs


/**
 * @Author: jiangKunKun
 * @CreateDate: 2025/10/14
 * @Description:
 *  波浪侧边栏 - 类似微信通讯录的字母索引控件
 *  功能特性：
 *  支持左右两侧显示
 *  可自定义文本颜色、对齐方式
 *  懒响应模式避免重复触发
 *  触摸选择字母回调
 * @SpittingGrooves:
 */
class WaveSideBar @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    companion object {
        private const val DEFAULT_TEXT_SIZE = 14 // sp
        private const val DEFAULT_MAX_OFFSET = 80 // dp

        /** 右侧位置常量 */
        const val POSITION_RIGHT = 0
        /** 左侧位置常量 */
        const val POSITION_LEFT = 1
        /** 文本居中对齐 */
        const val TEXT_ALIGN_CENTER = 0
        /** 文本左对齐 */
        const val TEXT_ALIGN_LEFT = 1
        /** 文本右对齐 */
        const val TEXT_ALIGN_RIGHT = 2
    }

    private val DEFAULT_INDEX_ITEMS = arrayOf("A", "B", "C", "D", "E", "F", "G", "H", "I",
        "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z")

    /**
     * 索引项数组，存储要显示的字符
     * @see setIndexItems
     */
    private var mIndexItems = DEFAULT_INDEX_ITEMS

    /**
     * 当前选中的索引，手指抬起时重置为-1
     */
    private var mCurrentIndex = -1

    /**
     * 手指触摸点的Y坐标，基准线是触摸区域的顶部
     * 手指抬起时重置为-1
     */
    private var mCurrentY = -1f

    /**
     * 普通文本绘制画笔
     * @see init
     */
    private val mPaint = Paint()

    /**
     * 字母文本颜色
     * @see setTextColor
     */
    private var mTextColor = Color.GRAY

    /**
     * 文字大小
     * @see setTextSize
     */
    private var mTextSize: Float = 0f

    /**
     * 每个索引项的高度
     */
    private var mIndexItemHeight = 0f

    /**
     * 最大偏移量
     * @see setMaxOffset
     */
    private var mMaxOffset: Float = 0f

    /**
     * 触摸区域，当ACTION_DOWN发生在此区域内时，侧边栏开始工作
     */
    private val mStartTouchingArea = RectF()

    /**
     * 触摸区域的高度和宽度
     */
    private var mBarHeight = 0f
    private var mBarWidth = 0f

    /**
     * 标记手指是否开始触摸
     * true表示ACTION_DOWN已发生但ACTION_UP尚未发生
     */
    private var mStartTouching = false

    /**
     * 是否启用懒响应模式
     * true: 只有当手指抬起时才触发回调
     * false: 手指按下、抬起和移动时都会触发回调
     * @see setLazyRespond
     */
    private var mLazyRespond = false

    /**
     * 控件显示位置，左侧或右侧
     * @see setPosition
     */
    private var mSideBarPosition = POSITION_RIGHT

    /**
     * 文本对齐方式
     * @see setTextAlign
     */
    private var mTextAlignment = TEXT_ALIGN_CENTER

    /**
     * 字母选择监听器
     * @see setOnSelectIndexItemListener
     */
    private var mOnSelectIndexItemListener: ((text: String) -> Unit)? = null

    /**
     * 第一个索引项文本绘制的基准线Y坐标
     */
    private var mFirstItemBaseLineY = 0f

    /**
     * 用于dp和sp转px
     */
    private val mDisplayMetrics = context.resources.displayMetrics

    init {
        init(context, attrs)
        isClickable = true
    }

    private fun init(context: Context, attrs: AttributeSet?) {
        // 获取属性
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaveSideBar)
        mLazyRespond = typedArray.getBoolean(R.styleable.WaveSideBar_sidebar_lazy_respond, false)
        mTextColor = typedArray.getColor(R.styleable.WaveSideBar_sidebar_text_color, Color.GRAY)
        mTextSize = typedArray.getDimension(R.styleable.WaveSideBar_sidebar_text_size, sp2px(DEFAULT_TEXT_SIZE))
        mMaxOffset = typedArray.getDimension(R.styleable.WaveSideBar_sidebar_max_offset, dp2px(DEFAULT_MAX_OFFSET))
        mSideBarPosition = typedArray.getInt(R.styleable.WaveSideBar_sidebar_position, POSITION_RIGHT)
        mTextAlignment = typedArray.getInt(R.styleable.WaveSideBar_sidebar_text_alignment, TEXT_ALIGN_CENTER)
        typedArray.recycle()

        // 初始化画笔
        mPaint.isAntiAlias = true
        mPaint.color = mTextColor
        mPaint.textSize = mTextSize
        setTextAlign(mTextAlignment)
    }

    private fun sp2px(sp: Int): Float {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp.toFloat(), mDisplayMetrics)
    }

    private fun dp2px(dp: Int): Float {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), mDisplayMetrics)
    }

    /**
     * 设置字母文本颜色
     * @param color 颜色值
     */
    fun setTextColor(color: Int) {
        mTextColor = color
        mPaint.color = color
        invalidate()
    }

    /**
     * 设置最大偏移量
     * @param offset 偏移量值(dp)
     */
    fun setMaxOffset(offset: Int) {
        mMaxOffset = dp2px(offset)
        invalidate()
    }

    /**
     * 设置控件显示位置
     * @param position POSITION_LEFT 或 POSITION_RIGHT
     */
    fun setPosition(position: Int) {
        if (position != POSITION_RIGHT && position != POSITION_LEFT) {
            throw IllegalArgumentException("the position must be POSITION_RIGHT or POSITION_LEFT")
        }
        mSideBarPosition = position
        requestLayout()
    }

    /**
     * 设置文本对齐方式
     * @param align TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT, TEXT_ALIGN_RIGHT
     */
    fun setTextAlign(align: Int) {
        mTextAlignment = align
        when (align) {
            TEXT_ALIGN_CENTER -> mPaint.textAlign = Paint.Align.CENTER
            TEXT_ALIGN_LEFT -> mPaint.textAlign = Paint.Align.LEFT
            TEXT_ALIGN_RIGHT -> mPaint.textAlign = Paint.Align.RIGHT
        }
        invalidate()
    }

    /**
     * 设置文字大小
     * @param size 文字大小(sp)
     */
    fun setTextSize(size: Int) {
        mTextSize = sp2px(size)
        mPaint.textSize = mTextSize
        requestLayout()
    }

    /**
     * 设置是否启用懒响应模式
     * @param lazyRespond true启用懒响应，false禁用
     */
    fun setLazyRespond(lazyRespond: Boolean) {
        mLazyRespond = lazyRespond
    }

    /**
     * 设置索引项内容
     * @param items 可变参数的字符串数组
     */
    fun setIndexItems(vararg items: String) {
        mIndexItems = items.toList().toTypedArray()
        requestLayout()
    }

    /**
     * 设置字母选择监听器
     * @param listener Lambda表达式回调函数
     */
    fun setOnSelectIndexItemListener(listener: (text: String) -> Unit) {
        mOnSelectIndexItemListener = listener
    }

    /**
     * 测量控件尺寸
     * @param widthMeasureSpec 宽度测量规范
     * @param heightMeasureSpec 高度测量规范
     */
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        val height = MeasureSpec.getSize(heightMeasureSpec)
        val width = MeasureSpec.getSize(widthMeasureSpec)

        val fontMetrics = mPaint.fontMetrics
        mIndexItemHeight = fontMetrics.bottom - fontMetrics.top
        mBarHeight = mIndexItems.size * mIndexItemHeight + 40

        // 计算最长文本的宽度作为侧边栏宽度
        for (indexItem in mIndexItems) {
            mBarWidth = maxOf(mBarWidth, mPaint.measureText(indexItem))
        }

        val areaLeft = if (mSideBarPosition == POSITION_LEFT) {
            0f
        } else {
            width - mBarWidth - paddingRight
        }
        val areaRight = if (mSideBarPosition == POSITION_LEFT) {
            paddingLeft + areaLeft + mBarWidth
        } else {
            width.toFloat()
        }
        val areaTop = height / 2f - mBarHeight / 2f
        val areaBottom = areaTop + mBarHeight
        mStartTouchingArea.set(areaLeft, areaTop, areaRight, areaBottom)

        // 第一个项目的文本基准线Y
        mFirstItemBaseLineY = (height / 2f - mIndexItems.size * mIndexItemHeight / 2f) +
                (mIndexItemHeight / 2f - (fontMetrics.descent - fontMetrics.ascent) / 2f) -
                fontMetrics.ascent

        setMeasuredDimension(width, height)
    }

    /**
     * 绘制控件内容
     * @param canvas 画布对象
     */
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // 绘制每个字母
        for (i in mIndexItems.indices) {
            val baseLineY = mFirstItemBaseLineY + mIndexItemHeight * i

            // 计算要绘制的项目的缩放因子
            val scale = getItemScale(i)

            val alphaScale = if (i == mCurrentIndex) 255 else (255 * (1 - scale)).toInt()
            mPaint.alpha = alphaScale.coerceIn(0, 255)

            mPaint.textSize = mTextSize + mTextSize * scale

            val baseLineX = when (mSideBarPosition) {
                POSITION_LEFT -> {
                    when (mTextAlignment) {
                        TEXT_ALIGN_CENTER -> paddingLeft + mBarWidth / 2 + mMaxOffset * scale
                        TEXT_ALIGN_LEFT -> paddingLeft + mMaxOffset * scale
                        TEXT_ALIGN_RIGHT -> paddingLeft + mBarWidth + mMaxOffset * scale
                        else -> 0f
                    }
                }
                else -> {
                    when (mTextAlignment) {
                        TEXT_ALIGN_CENTER -> width - paddingRight - mBarWidth / 2 - mMaxOffset * scale
                        TEXT_ALIGN_RIGHT -> width - paddingRight - mMaxOffset * scale
                        TEXT_ALIGN_LEFT -> width - paddingRight - mBarWidth - mMaxOffset * scale
                        else -> 0f
                    }
                }
            }

            // 绘制
            canvas.drawText(mIndexItems[i], baseLineX, baseLineY, mPaint)
        }

        // 重置画笔
        mPaint.alpha = 255
        mPaint.textSize = mTextSize
    }

    /**
     * 计算要绘制的项目的缩放因子
     *
     * @param index 项目在数组中的索引
     * @return 项目的缩放因子
     */
    private fun getItemScale(index: Int): Float {
        var scale = 0f
        if (mCurrentIndex != -1) {
            val distance = abs(mCurrentY - (mIndexItemHeight * index + mIndexItemHeight / 2)) / mIndexItemHeight
            scale = 1 - distance * distance / 16
            scale = maxOf(scale, 0f)
        }
        return scale
    }

    /**
     * 处理触摸事件
     * @param event 触摸事件对象
     * @return 是否消费该事件
     */
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (mIndexItems.isEmpty()) {
            return super.onTouchEvent(event)
        }

        val eventY = event.y
        val eventX = event.x
        mCurrentIndex = getSelectedIndex(eventY)

        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                if (mStartTouchingArea.contains(eventX, eventY)) {
                    mStartTouching = true
                    if (!mLazyRespond && mOnSelectIndexItemListener != null) {
                        mOnSelectIndexItemListener?.invoke(mIndexItems[mCurrentIndex])
                    }
                    invalidate()
                    return true
                } else {
                    mCurrentIndex = -1
                    return false
                }
            }

            MotionEvent.ACTION_MOVE -> {
                if (mStartTouching && !mLazyRespond && mOnSelectIndexItemListener != null) {
                    mOnSelectIndexItemListener?.invoke(mIndexItems[mCurrentIndex])
                }
                invalidate()
                return true
            }

            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                if (mLazyRespond && mOnSelectIndexItemListener != null) {
                    mOnSelectIndexItemListener?.invoke(mIndexItems[mCurrentIndex])
                }
                mCurrentIndex = -1
                mStartTouching = false
                invalidate()
                return true
            }
        }

        return super.onTouchEvent(event)
    }

    private fun getSelectedIndex(eventY: Float): Int {
        mCurrentY = eventY - (height / 2f - mBarHeight / 2f)
        if (mCurrentY <= 0) {
            return 0
        }

        var index = (mCurrentY / mIndexItemHeight).toInt()
        if (index >= mIndexItems.size) {
            index = mIndexItems.size - 1
        }
        return index
    }
}